package wx

import (
	"context"
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/core/option"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/app"
	"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
	wxutils "github.com/wechatpay-apiv3/wechatpay-go/utils"
	"gitlab.local/backend/utils"
	"os"
	"pay/bll/infra"
	log "pay/collector/logger"
	"pay/config"
	"pay/errors"
	"strconv"
	"time"
)

type ReqHeaders struct {
	Serial    string `header:"Wechatpay-Serial" binding:"required"`
	Signature string `header:"Wechatpay-Signature" binding:"required"`
	Timestamp string `header:"Wechatpay-Timestamp" binding:"required"`
	Nonce     string `header:"Wechatpay-Nonce" binding:"required"`
	RequestId string `header:"Request-ID"`
}

type WxPay struct {
	conf          *config.WxPayConf
	mchPrivateKey *rsa.PrivateKey
	client        *core.Client
	apiSvc        *app.AppApiService
	refundSvc     *refunddomestic.RefundsApiService
	currency      string
}

func NewWxPay(conf *config.WxPayConf) (*WxPay, error) {
	fmt.Println(utils.JsonStr(conf))

	if !conf.Enable {
		return &WxPay{conf: conf}, nil
	}

	privateKey, err := wxutils.LoadPrivateKeyWithPath(conf.PrivateKeyPath)
	if err != nil {
		return nil, err
	}
	opts := []core.ClientOption{
		option.WithWechatPayAutoAuthCipher(conf.MchId, conf.MchCertSerialNum, privateKey, conf.ApiV3Key),
	}
	client, err := core.NewClient(context.Background(), opts...)
	if err != nil {
		return nil, err
	}

	pay := &WxPay{
		conf:          conf,
		mchPrivateKey: privateKey,
		client:        client,
		currency:      "CNY", // 人民币
		apiSvc:        &app.AppApiService{Client: client},
		refundSvc:     &refunddomestic.RefundsApiService{Client: client},
	}

	return pay, nil
}

// ValidateSignature 验证微信支付回调请求的签名
//func (p *WxPay) ValidateSignature(c *gin.Context, body string) (ok bool, err error) {
//	var headers ReqHeaders
//	if err := c.ShouldBindHeader(&headers); err != nil {
//		return false, err
//	}
//
//	// 验证证书序列号
//	if headers.Serial != p.conf.MchCertSerialNum {
//		return false, errors.New("商户证书序列号错误")
//	}
//	// 验证时间戳
//	timestamp, err := strconv.ParseInt(headers.Timestamp, 10, 64)
//	if err != nil {
//		return false, errors.New("时间戳格式错误")
//	}
//	tsDiff := time.Now().Unix() - timestamp
//	if tsDiff < -10 {
//		return false, errors.New("时间戳超前")
//	} else if tsDiff > infra.MaxTimestampSpan {
//		return false, errors.New("时间戳过期")
//	}
//
//	// 构造签名字符串，准备验签
//	signStr := fmt.Sprintf("%s\n%s\n%s\n", headers.Timestamp, headers.Nonce, body)
//
//	pubKeyBytes, err := os.ReadFile(p.conf.PublicKeyPath)
//	if err != nil {
//		log.Errorf("read public key file err:%v", err)
//		return false, err
//	}
//	return utils.VerySignWithBase64(signStr, headers.Signature, string(pubKeyBytes))
//}

func (p *WxPay) ValidateSignature(headers ReqHeaders, body string) (ok bool, err error) {

	// 验证证书序列号
	if headers.Serial != p.conf.MchCertSerialNum {
		return false, errors.New("商户证书序列号错误")
	}
	// 验证时间戳
	timestamp, err := strconv.ParseInt(headers.Timestamp, 10, 64)
	if err != nil {
		return false, errors.New("时间戳格式错误")
	}
	tsDiff := time.Now().Unix() - timestamp
	if tsDiff < -10 {
		return false, errors.New("时间戳超前")
	} else if tsDiff > infra.MaxTimestampSpan {
		return false, errors.New("时间戳过期")
	}

	// 构造签名字符串，准备验签
	signStr := fmt.Sprintf("%s\n%s\n%s\n", headers.Timestamp, headers.Nonce, body)

	pubKeyBytes, err := os.ReadFile(p.conf.PublicKeyPath)
	if err != nil {
		log.Errorf("read public key file err:%v", err)
		return false, err
	}
	return utils.VerySignWithBase64(signStr, headers.Signature, string(pubKeyBytes))
}

func (p *WxPay) Decrypt(rs *Resource) (plaintext string, data *ResourceContent, err error) {
	// 依官网文档 https://pay.weixin.qq.com/docs/merchant/apis/in-app-payment/payment-notice.html
	// 加密算法现只有 AEAD_AES_256_GCM，故不情况判断
	plaintext, err = wxutils.DecryptAES256GCM(p.conf.ApiV3Key, rs.AssociatedData, rs.Nonce, rs.Ciphertext)
	if err != nil {
		return "", nil, err
	}
	var content ResourceContent
	if err = json.Unmarshal([]byte(plaintext), &content); err != nil {
		return "", nil, err
	}
	data = &content
	return
}

// Prepay 创建订单，返回预付单标识
func (p *WxPay) Prepay(ctx context.Context, outTradeNo, desc string, fen int64, orderType string) (prepayId string, expireTs int64, err error) {
	expireTime, _ := time.Parse(time.RFC3339, time.Now().Add(infra.PayExpireDuration).Format(time.RFC3339))
	supportInvoice := false
	attach := infra.Attach{OrderType: orderType}.Json()
	params := app.PrepayRequest{
		Appid:         &p.conf.AppId,     // app id
		Mchid:         &p.conf.MchId,     // 商户 id
		Description:   &desc,             // 商品描述
		OutTradeNo:    &outTradeNo,       // 业务订单号
		TimeExpire:    &expireTime,       // 支付过期失效时间
		NotifyUrl:     &p.conf.NotifyURL, // 支付回调URL
		LimitPay:      nil,               // 支付方式
		SupportFapiao: &supportInvoice,   // 是否支持发票
		Amount: &app.Amount{ // 支付金额
			Total:    &fen,
			Currency: &p.currency,
		},
		Detail:     nil,
		SceneInfo:  nil,
		SettleInfo: nil,
		Attach:     &attach, // 附加信息
	}
	resp, _, err := p.apiSvc.Prepay(ctx, params)
	if err != nil {
		return "", 0, err
	}
	return *resp.PrepayId, expireTime.Unix(), nil
}

// Close 关闭订单
func (p *WxPay) Close(ctx context.Context, outTradeNo string) error {
	params := app.CloseOrderRequest{
		OutTradeNo: &outTradeNo,
		Mchid:      &p.conf.MchId,
	}
	_, err := p.apiSvc.CloseOrder(ctx, params)
	return err
}

// Refund 退款，可全部退款，也可部分退款
func (p *WxPay) Refund(ctx context.Context, transactionId, outRefundNo, reason string, originalAmount, refundAmount int64) (*refunddomestic.Refund, error) {
	params := refunddomestic.CreateRequest{
		SubMchid:      nil,
		TransactionId: &transactionId,
		OutRefundNo:   &outRefundNo,
		Reason:        &reason,
		Amount: &refunddomestic.AmountReq{
			Refund:   &refundAmount,
			Total:    &originalAmount,
			Currency: &p.currency,
		},
		GoodsDetail: nil,
	}
	refund, _, err := p.refundSvc.Create(ctx, params)
	if err != nil {
		return nil, err
	}
	return refund, nil
}
